En python todo es un objeto.
Una variable es también un objeto, es decir, una instancia en memoria de una clase.
>>> a = 42
>>> a
42
>>> type(a)
<class 'int'>>>> a = 42
>>> a
42
>>> type(a)
<class 'int'>a es un puntero que apunta a una instancia de la clase int cuyo valor en memoria es 42
>>> b = a # b es un apuntador al mismo valor que a
>>> b is a
True
>>> id(a) # dirección de memoria
94309428040480
>>> id(b) # dirección de memoria
94309428040480
>>> del a # elimina el apuntador a. El contenido sigue en b
>>> b
42
>>> b is a
False>>> b = a # b es un apuntador al mismo valor que a
>>> b is a
True
>>> id(a) # dirección de memoria
94309428040480
>>> id(b) # dirección de memoria
94309428040480
>>> del a # elimina el apuntador a. El contenido sigue en b
>>> b
42
>>> b is a
FalseAl asignar un nuevo valor a una variable, se está reasignando un nuevo contenido. La memoria ocupada por el anterior valor será liberada cuando el recolector de basura determine.
>>> a = 42 # asigna memoria para el valor 42 >>> id(a) 94309428041760 >>> a = 23 # asigna nueva memoria para el valor 23 >>> id(a) 94309428041472
>>> a = 42 # asigna memoria para el valor 42
>>> id(a)
94309428041760
>>> a = 23 # asigna nueva memoria para el valor 23
>>> id(a)
94309428041472Por temas de rendimiento, una variable es mutable si se puede cambiar su valor en memoria, o no mutable si se crea otra asignación de memoria y se cambia el puntero.
Variable no mutable, reasignación:
>>> a = (1,) >>> id(a) 139803025005584 >>> a+=(2,) >>> id(a) 139803001628064 >>> a+=(3,) >>> id(a) 139803024299744 >>> a (1, 2, 3)
>>> a = (1,)
>>> id(a)
139803025005584
>>> a+=(2,)
>>> id(a)
139803001628064
>>> a+=(3,)
>>> id(a)
139803024299744
>>> a
(1, 2, 3)Variable mutable, cambio en memoria:
>>> a = [1] >>> id(a) 139803001791344 >>> a+=[2] >>> id(a) 139803001791344 >>> a+=[3] >>> id(a) 139803001791344 >>> a [1, 2, 3]
>>> a = [1]
>>> id(a)
139803001791344
>>> a+=[2]
>>> id(a)
139803001791344
>>> a+=[3]
>>> id(a)
139803001791344
>>> a
[1, 2, 3]Mejor rendimiento en una n-lista que en una n-tupla. Aunque no son equivalentes, cada estructura tiene usos distintos.
Los métodos sobre variables mutables devuelven nada None, pues modifican el propio objeto.
>>> a = [1,3,2] >>> print(a.sort()) # ordena la lista y devuelve nada None >>> a [1, 2, 3]
>>> a = [1,3,2]
>>> print(a.sort()) # ordena la lista y devuelve nada
None
>>> a
[1, 2, 3]Los métodos sobre variables no-mutables devuelven un nuevo objeto, y dejan sin modificar el original
>>> a = "Ejemplo" >>> b = a.lower() # devuelve un puntero a un nuevo objeto >>> a 'Ejemplo' >>> b 'ejemplo'
>>> a = "Ejemplo"
>>> b = a.lower() # devuelve un puntero a un nuevo objeto
>>> a
'Ejemplo'
>>> b
'ejemplo'Error común, asignar el resultado de una operación mutable (none) y perder el objeto
>>> a = [1,3,2] >>> a = a.sort() # ordena la lista y devuelve None >>> a None # hemos perdido la lista :(
>>> a = [1,3,2]
>>> a = a.sort() # ordena la lista y devuelve None
>>> a
None # hemos perdido la lista :(Las variables no se declaran, por lo tanto si se usan dentro de un bloque, pudiera ser que fuera no existieran, por ejemplo:
>>> if False: ... c=1 ... >>> c Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'c' is not defined
>>> if False:
... c=1
...
>>> c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'c' is not definedLa variable c no existe pues se define en un bloque que nunca entra.
Para saber si una variable está definida:
>>> if 'a' in globals().keys():
del a>>> if 'a' in globals().keys():
del a>>> a = 42 # variable global
>>> def f():
... a = 34 # variable nueva, local a f()
print(a, globals.get('a')) # 34 42
...
>>> f()
>>> a # variable global
42>>> a = 42 # variable global
>>> def f():
... a = 34 # variable nueva, local a f()
print(a, globals.get('a')) # 34 42
...
>>> f()
>>> a # variable global
42No se permite cambiar el valor de una variable global desde dentro de una función.
se declaran con def nombre([param]):
Es recomendable documentar en la 1ª línea usando cadenas 3 comillas """Función que hace foo y devuelve faa""""
>>> def f(a,b,c): ... """Devuelve la suma de los tres parámetros""" ... return a+b+c ... >>> f(2,3,4) 9 >>> def f(a,b,c=0): ... """Devuelve suma de 3 parámetros, el c es opcional""" ... return a+b+c ... >>> f(2,4) 6 >>> def f(a=0,b=0,c=0): ... """Devuelve suma de 3 parámetros opcionales""" ... return a+b+c ... >>> f() 0 >>> f(2,4) 6 >>> f(2,3,4) 9 >>> f(3) 3
>>> def f(a,b,c):
... """Devuelve la suma de los tres parámetros"""
... return a+b+c
...
>>> f(2,3,4)
9
>>> def f(a,b,c=0):
... """Devuelve suma de 3 parámetros, el c es opcional"""
... return a+b+c
...
>>> f(2,4)
6
>>> def f(a=0,b=0,c=0):
... """Devuelve suma de 3 parámetros opcionales"""
... return a+b+c
...
>>> f()
0
>>> f(2,4)
6
>>> f(2,3,4)
9
>>> f(3)
3Una función no debería tener efectos colaterales, cada vez que se llame sin argumentos, debe devolver el mismo resultado. Pero... si el argumento por defecto es mutable (por ejemplo una lista), en dos llamadas se estará usando la misma lista, modificada de la anterior llamada.
>>> def f(arg=[1,2]):
arg += (arg[-1] + 1,)
print(arg)
>>> f()
[1,2,3]
>>> f()
[1,2,3,4]>>> def f(arg=[1,2]):
arg += (arg[-1] + 1,)
print(arg)
>>> f()
[1,2,3]
>>> f()
[1,2,3,4]Por eso no es recomendable usar argumentos por defecto modificables. En caso necesario usar uno no-modificable (llamado centinela) y crear el valor por defecto en el cuerpo de la función:
>>> def f(arg=None):
if arg is None:
arg = [1,2]
arg += (arg[-1] + 1,)
print(arg)
>>> f()
[1,2,3]
>>> f()
[1,2,3]>>> def f(arg=None):
if arg is None:
arg = [1,2]
arg += (arg[-1] + 1,)
print(arg)
>>> f()
[1,2,3]
>>> f()
[1,2,3]Los parámetros opcionales van al final, si queremos dar valor al segundo parámetro sin dar valor al primero tendremos que nombrarlo.
>>> def f(a=0, b=0, c=0):
print(locals())
>>> f(0,4) # da valor a 'b' pero también ha de darlo a 'a'
{'a': 0, 'c': 0, 'b': 4}
>>> f(b=4) # da valor sólo a 'b'
{'a': 0, 'c': 0, 'b': 4}>>> def f(a=0, b=0, c=0):
print(locals())
>>> f(0,4) # da valor a 'b' pero también ha de darlo a 'a'
{'a': 0, 'c': 0, 'b': 4}
>>> f(b=4) # da valor sólo a 'b'
{'a': 0, 'c': 0, 'b': 4}Se pueden usar nombrados y no nombrados
# estas llamadas son equivalentes >>> f(1,2,3) >>> f(a=1, b=2, c=3) >>> f(b=2, c=3, a=1) >>> f(1, 2, c=3)
# estas llamadas son equivalentes
>>> f(1,2,3)
>>> f(a=1, b=2, c=3)
>>> f(b=2, c=3, a=1)
>>> f(1, 2, c=3)Los nombrados deben pasarse después de los nombrados, y no se debe declarar más de una vez cada argumento:
>>> f(a=1, 2, 3)
File "<input>", line 1
SyntaxError: non-keyword arg after keyword arg
>>> f(1, 2, a=3)
Traceback (most recent call last):
File "<input>", line 1, in <module>
f(1, 2, a=3)
TypeError: f() got multiple values for keyword argument 'a'
>>> >>> f(a=1, 2, 3)
File "<input>", line 1
SyntaxError: non-keyword arg after keyword arg
>>> f(1, 2, a=3)
Traceback (most recent call last):
File "<input>", line 1, in <module>
f(1, 2, a=3)
TypeError: f() got multiple values for keyword argument 'a'
>>> Para funciones que acepten un número variable de argumentos. declararemos los no nombrados en una n-tupla, y los nombrados en un diccionario:
>>> def f(*args): # n-tupla de parámetros no-nombrados
... return locals()
...
>>> f(1,2)
{'args': (1, 2)} # args es una tupla de 2 enteros (int,int)
>>> f(1,2,4.5,'a')
{'args': (1,2,4.5,'a')} # args es una tupla (int,int,float,char)
>>> def g(**kwargs): # diccionario de parámetros nombrados
... return locals()
...
>>> g(a=2,b=3)
{'kwargs': {'a': 2, 'b': 3}} # kwargs es un diccionario de 2 elementos
>>> g(a=2,b=3,c='a')
{'kwargs': {'a': 2, 'c': 'a', 'b': 3}} # es un diccionario de 3 elementos de distintos tipos
>>> >>> def f(*args): # n-tupla de parámetros no-nombrados
... return locals()
...
>>> f(1,2)
{'args': (1, 2)} # args es una tupla de 2 enteros (int,int)
>>> f(1,2,4.5,'a')
{'args': (1,2,4.5,'a')} # args es una tupla (int,int,float,char)
>>> def g(**kwargs): # diccionario de parámetros nombrados
... return locals()
...
>>> g(a=2,b=3)
{'kwargs': {'a': 2, 'b': 3}} # kwargs es un diccionario de 2 elementos
>>> g(a=2,b=3,c='a')
{'kwargs': {'a': 2, 'c': 'a', 'b': 3}} # es un diccionario de 3 elementos de distintos tipos
>>> Al ir nombrados se puede pasar del diccionario a variables unitarias usando:
>>> def g(**kwargs):
... locals().update(**kwargs)
... del kwargs
... return locals()
...
>>> g(a=2,b=3.3,c='hola')
{'a': 2, 'c': 'hola', 'b': 3.3}>>> def g(**kwargs):
... locals().update(**kwargs)
... del kwargs
... return locals()
...
>>> g(a=2,b=3.3,c='hola')
{'a': 2, 'c': 'hola', 'b': 3.3}Se puede combinar todos los tipos de parámetros en una misma función. En este caso hay que prestar atención al orden de los parámetros
>>> def f(a, b=0, *unnamed, **named): ... """un obligatorio, un opcional, una tupla variable sin nombre, un diccionario variable con nombre""" ... return a + b + sum(unnamed) + sum(named.values()) ... >>> f(1, 2, 3, 4, y=5, z=6) 21
>>> def f(a, b=0, *unnamed, **named):
... """un obligatorio, un opcional, una tupla variable sin nombre, un diccionario variable con nombre"""
... return a + b + sum(unnamed) + sum(named.values())
...
>>> f(1, 2, 3, 4, y=5, z=6)
21
Se denomina unpacking a la técnica de pasar una colección de argumentos no nombrados mediante un asterisco, y una colección de diccionario nombrado mediante dos asteriscos. Es útil en más sitios que en el paso de parámetros, por ejemplo, en el caso de querer fusionar varios diccionarios.
Nota, si en varios diccionarios se usa dos veces el mismo nombre de variable, únicamente tendrá validez la última (machaca los valores anteriores).
Una función que acepte absolutamente cualquier parámetro tendría esta firma: def f(*args, **kwargs):
Sin embargo, si hay parámetros obligatorios, habrá que ponerlos antes: def f(a, b, *args, **kwargs):
La ventaja es que si modificamos la firma de esta manera, mantendremos la compatibilidad con código anterior sin tener que revisar todo el código fuente.
El problema es que se pierde el control de las llamadas, es decir, el compilador no nos avisará si estamos llamando a un método con más parámetros que los necesarios. Es recomendable no usar los argumentos con asteriscos
pdte
En C++ se indica el tipo de los parámetros, en python no.
Puede ser una ventaja ya que dará igual que el tipo sea un apuntador a fichero, un id flujo, una cadena con la ruta del fichero, etc.
Puede ser un inconveniente ya que no se realiza control de tipos.
Con el sistema de anotaciones se puede comprobar el tipado de los parámetros.
>>> def f(a:str, b:int)->int: # Anotación de tipos esperados en parámetros y en retorno
... print(locals())
... return 0
...
>>> f('aaa',32) # llamada según lo esperado
{'a': 'aaa', 'b': 32}
0
>>> f(32,7) # llamada contra lo esperado, y se ejecuta igual
{'a': 32, 'b': 7}
0>>> def f(a:str, b:int)->int: # Anotación de tipos esperados en parámetros y en retorno
... print(locals())
... return 0
...
>>> f('aaa',32) # llamada según lo esperado
{'a': 'aaa', 'b': 32}
0
>>> f(32,7) # llamada contra lo esperado, y se ejecuta igual
{'a': 32, 'b': 7}
0No es fuertemente tipado, por lo que se ejecutará igual aunque no se cumpla.
La lista de anotaciones para ver los tipos esperados se encuentra en:
>>> f.__annotations__
{'a': <class 'str'>, 'b': <class 'int'>, 'return': <class 'int'>}>>> f.__annotations__
{'a': <class 'str'>, 'b': <class 'int'>, 'return': <class 'int'>}Para dar más información del tipado
PDTE
from typing import Sequence, Mapping
def f(lista: Sequence[str])->Mapping[str,int]:
passfrom typing import Sequence, Mapping
def f(lista: Sequence[str])->Mapping[str,int]:
passclass Nombre (clases padre)
>>> class A(): ... pass ... >>> type. mro(A) [<class '__main__.A'>, <class 'object'>]
>>> class A():
... pass
...
>>> type. mro(A)
[<class '__main__.A'>, <class 'object'>]Dice que la clase A es de tipo class y que su clase padre es 'object' (por defecto)
Dependen de la indentación.
En python todo es público, los atributos se almacenan en la lista que se accede con la primitiva dir y se accede mediante el punto .
>>> class A(): ... altura = 12 >>> A.altura 12 >>> 'altura' in dir(A) True
>>> class A():
... altura = 12
>>> A.altura
12
>>> 'altura' in dir(A)
TrueEn python todo es un objeto, los atributos son objetos, los métodos son objetos, todo.
Se declaran igual que una función, aunque indentados en el bloque del cuerpo de la clase.
>>> class A(): ... altura=12 ... def ff(self): pass ... >>> type(A.ff) <class 'function'>
>>> class A():
... altura=12
... def ff(self): pass
...
>>> type(A.ff)
<class 'function'>Hay 3 tipos de métodos:
No hay que usar new, simplemente:
>>> a = A() >>> a.altura 12 >>> a.altura = 10 >>> a.altura 10 >>> b = A() # otra instancia a la misma clase >>> b.altura 12 >>> c = a # dos punteros a la misma instancia >>> c.altura 10 >>> del a # elimina el puntero, pero no la instancia >>> a.altura Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'a' is not defined >>> c.altura # la instancia sigue activa en el puntero c 10
>>> a = A()
>>> a.altura
12
>>> a.altura = 10
>>> a.altura
10
>>> b = A() # otra instancia a la misma clase
>>> b.altura
12
>>> c = a # dos punteros a la misma instancia
>>> c.altura
10
>>> del a # elimina el puntero, pero no la instancia
>>> a.altura
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> c.altura # la instancia sigue activa en el puntero c
10Un módulo es un conjunto de funcionalidades agrupadas en un mismo bloque.
Se ejecuta directamente y se considera un punto de entrada de la aplicación.
Hay buenas prácticas para su diseño.
Puede ser un fichero .py o .pyc (si está compilado).
Un fichero programado en C (para CPython)
O una carpeta.
Es un bloque con su propio espacio de nombres y que contiene código y/o otros módulos.
El archivo init.py es obligatorio en python2 pero no en python3, donde sólo es útil para declarar algunas variables.
El código de cada módulo es independiente y son estancos: sólo se puede acceder al contenido desde el propio módulo.
Para poder usar un módulo desde otro módulo hay que importarlo, usando import, esa instrucción crea una variable contenedor con el mismo nombre que el módulo, que apunta al módulo (que, como todo en python, también es un objeto). Con esa variable se puede acceder al contenido.
import mi_modulo mi_modulo.mi_funcion()
import mi_modulo
mi_modulo.mi_funcion()Importación parcial podemos importar sólo las funciones que nos interesen. En ese caso se crea una variable local con el nombre de la función importada que apunta directamente a esa función
from mi_modulo import mi_funcion
from mi_modulo import mi_funcionAlias de funciones cambia el nombre de la variable local, usando un alias que seguirá apuntando a la función
>>> from math import sqrt as raiz >>> raiz <built-in function sqrt> >>> raiz(9) 3.0 >>> raiz(2) 1.4142135623730951
>>> from math import sqrt as raiz
>>> raiz
<built-in function sqrt>
>>> raiz(9)
3.0
>>> raiz(2)
1.4142135623730951Alias de módulo cambia el nombre de la variable local que apunta al módulo importado
>>> import cmath as math_complejos >>> math_complejos <module 'cmath' (built-in)>
>>> import cmath as math_complejos
>>> math_complejos
<module 'cmath' (built-in)>Punto de entrada si el módulo se importa para usar en otro módulo, o si el módulo se ejecuta directamente python3 mi_modulo.py
En el fichero de código tendremos un if para determinar cuando se ejecuta o cuando se importa:
if __name__ == "__main__":
# instrucciones para cuando el módulo es el punto de entrada
else:
# instrucciones para cuando el módulo está siendo importadoif __name__ == "__main__":
# instrucciones para cuando el módulo es el punto de entrada
else:
# instrucciones para cuando el módulo está siendo importadoImport *
Cuando se importa un módulo usando
from mi_modulo import *
from mi_modulo import *Se importará todas las funciones/variables/objetos declarados en la variable __all__ al principio de ese módulo:
__all__ = ['foo', 'faa', 'MiClase', ...]
__all__ = ['foo', 'faa', 'MiClase', ...]Lo más seguro es abrir el módulo y leer el código, tambien es lo más lento.
Suponiendo que esté bien documentado, se puede hacer también con dir y help
>>> import cmath >>> dir(cmath) ['__doc__', '__name__', '__package__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh', 'cos', 'cosh', 'e', 'exp', 'isinf', 'isnan', 'log', 'log10', 'phase', 'pi', 'polar', 'rect', 'sin', 'sinh', 'sqrt', 'tan', 'tanh']
>>> import cmath
>>> dir(cmath)
['__doc__', '__name__', '__package__', 'acos', 'acosh', 'asin', 'asinh', 'atan',
'atanh', 'cos', 'cosh', 'e', 'exp', 'isinf', 'isnan', 'log', 'log10', 'phase',
'pi', 'polar', 'rect', 'sin', 'sinh', 'sqrt', 'tan', 'tanh']Con help(cmath) se abre una ventana man del módulo, y también se puede pedir ayuda específica de una función help(cmath.sqrt)
Al arrancar python se arranca la máquina virtual (M.V.), se carga el módulo de entrada y los módulos de importación (de forma recursiva), para cada uno de ellos se hace un ánalisis sintáctico y se compila a un lenguaje entendible por la M.V.
Se compila todo el módulo aunque sólo se importe una función.
No hay que controlar reimportaciones, aunque se importe dos veces el mismo módulo sólo se compilará la primera vez.
Los compilados tienen extensión .pyc y se procesan en la carpeta __pycache__ para evitar ensuciar las carpetas con archivos no fuentes.
Python comprueba si el código es más reciente que el archivo compilado, sólo volverá a compilar los archivos necesarios.
Un archivo compilador con python 3.3 tiene distinta versión que un archivo compilado con python 3.4.
Dos niveles de optimización
python mi_modulo.py python -O mi_modulo.py python -OO mi_modulo.py
python mi_modulo.py
python -O mi_modulo.py
python -OO mi_modulo.pyDan lugar a nombres de compilados distintos
cpython sólo es una versión de python entre muchas, por eso compila sin machacar otros compilados
Un mismo módulo puede tener muchos compilados: